"""
参考sa-token StpUtil实现
"""
import time

from app.constants import REDIS_KEY_USER_TOKEN, REDIS_KEY_TOKEN_LAST_ACTIVE
from app.core.config import settings
from app.core.redis import redis_client as redis
from app.enums.error import TokenErrorEnum
from app.exception import TokenException

# 常量，表示一个 key 永不过期 （在一个 key 被标注为永远不过期时返回此值）
_NEVER_EXPIRE = -1
# 常量，表示系统中不存在这个缓存（在对不存在的 key 获取剩余存活时间时返回此值）
_NOT_VALUE_EXPIRE = -2


async def check_login(token: str) -> int:
    """
    检验当前会话是否已经登录，如未登录，则抛出异常

    :param token:
    :return:
    """
    # 1.查找此 token 对应的 loginId，如果找不到则抛出：token 无效
    login_id: str = await get_login_id(token)
    if login_id is None:
        raise TokenException(TokenErrorEnum.INVALID_TOKEN)

    # 2.如果这个 token 指向的是值是：过期标记，则抛出：token 已过期
    if login_id == TokenErrorEnum.TOKEN_TIMEOUT.type_:
        raise TokenException(TokenErrorEnum.TOKEN_TIMEOUT)

    # 3.如果这个 token 指向的是值是：被顶替标记，则抛出：token 已被顶下线
    if login_id == TokenErrorEnum.BE_REPLACED.type_:
        raise TokenException(TokenErrorEnum.BE_REPLACED)

    # 4.如果这个 token 指向的是值是：被踢下线标记，则抛出：token 已被踢下线
    if login_id == TokenErrorEnum.KICK_OUT.type_:
        raise TokenException(TokenErrorEnum.KICK_OUT)

    # 5.token 活跃频率检查
    await check_active_timeout_by_config(token)

    return int(login_id)


async def get_login_id(token: str) -> str:
    """ 查找此 token 对应的 loginId，如果找不到则抛出：token 无效 """
    return await redis.get(f"{REDIS_KEY_USER_TOKEN}:{token}")


async def check_active_timeout_by_config(token: str):
    """ 根据全局配置决定是否校验指定 token 的活跃度 """
    if not is_open_check_active_timeout():
        return

    # 1.检查此 token 的最后活跃时间是否已经超过了 active-timeout 的限制，如果是则代表其已被冻结，需要抛出：token 已被冻结
    await check_active_timeout(token)

    # 2.如果配置了自动续签功能, 则: 更新这个 token 的最后活跃时间 （注意此处的续签是在续 active-timeout，而非 timeout）
    if settings.sa_token_auto_renew:
        await update_last_active_to_now(token)


def is_open_check_active_timeout() -> bool:
    """ 是否开启检查active_timeout的值 """
    return (settings.sa_token_active_timeout != -1) or settings.sa_token_dynamic_active_timeout


async def check_active_timeout(token: str):
    """ 检查token的最后活跃时间是否已经超过了active-timeout的限制 """
    if await check_freeze(token):
        raise TokenException(TokenErrorEnum.TOKEN_FREEZE)


async def check_freeze(token: str) -> bool:
    """ 检查token是否被冻结 """
    # 1.获取这个 token 的剩余活跃有效期
    active_timeout = await get_token_active_timeout_by_token(token)

    # 2.值为 -1 代表此 token 已经被设置永不冻结
    if active_timeout == _NEVER_EXPIRE:
        return False

    # 3.值为 -2 代表已被冻结
    if active_timeout == _NOT_VALUE_EXPIRE:
        return True

    return False


async def get_token_active_timeout_by_token(token: str) -> int:
    """ 获取token的剩余活跃有效期 """
    # 如果全局配置了永不冻结，则返回 -1
    if not is_open_check_active_timeout():
        return _NEVER_EXPIRE

    # 先获取这个 token 的最后活跃时间，13位时间戳
    last_active_time = await get_token_last_active_time(token)
    if last_active_time == _NOT_VALUE_EXPIRE:
        return _NOT_VALUE_EXPIRE

    # 实际时间差
    time_diff = (int(time.time() * 1000) - last_active_time) / 1000
    # 该 token 允许的时间差
    allow_time_diff = await get_token_use_active_timeout_or_global_config(token)
    if allow_time_diff == _NEVER_EXPIRE:
        # 如果允许的时间差为 -1 ，则代表永不冻结，此处需要立即返回 -1 ，无需后续计算
        return _NEVER_EXPIRE

    # 校验这个时间差是否超过了允许的值
    #    计算公式为: 允许的最大时间差 - 实际时间差，判断是否 < 0， 如果是则代表已经被冻结 ，返回-2
    active_timeout = allow_time_diff - time_diff
    if active_timeout < 0:
        return -2

    # 否则代表没冻结，返回剩余有效时间
    return int(active_timeout)


async def get_token_last_active_time(token: str) -> int:
    """ 获取指定 token 的最后活跃时间（13位时间戳），如果不存在则返回 -2 """
    # 1.如果提供的 token 为 null，则返回 -2
    if token is None or token == "":
        return _NOT_VALUE_EXPIRE

    # 2.获取这个 token 的最后活跃时间，13位时间戳
    last_active_time = await redis.get(f"{REDIS_KEY_TOKEN_LAST_ACTIVE}:{token}")

    # 3.查不到，返回-2
    if last_active_time is None:
        return _NOT_VALUE_EXPIRE

    # 4.根据逗号切割字符串
    try:
        value_array = last_active_time.split(",")
        if value_array[0]:
            return int(value_array[0])
        return _NOT_VALUE_EXPIRE
    except (ValueError, IndexError):
        return _NOT_VALUE_EXPIRE


async def get_token_use_active_timeout_or_global_config(token: str) -> int:
    """ 获取指定 token 在缓存中的 activeTimeout 值，如果不存在则返回全局配置的 activeTimeout 值 """
    active_timeout = await get_token_use_active_timeout(token)
    if active_timeout is not None:
        return active_timeout

    return _NEVER_EXPIRE


async def get_token_use_active_timeout(token: str) -> int | None:
    """ 获取指定 token 在缓存中的 activeTimeout 值，如果不存在则返回 null """
    # 在未启用动态 activeTimeout 功能时，直接返回 null
    if not settings.sa_token_dynamic_active_timeout:
        return None

    # 先取出这个 token 的最后活跃时间值
    value = await redis.get(f"{REDIS_KEY_TOKEN_LAST_ACTIVE}:{token}")
    if not value:
        return None

    # 解析，无值的情况下返回 null
    try:
        value_array = value.split(",")
        if len(value_array) >= 2 and value_array[1]:
            return int(value_array[1])
        return None
    except (ValueError, IndexError):
        return None


async def update_last_active_to_now(token: str):
    """ 更新这个token的最后活跃时间 """
    key = f"{REDIS_KEY_TOKEN_LAST_ACTIVE}:{token}"
    value = f"{int(time.time() * 1000)},{get_token_use_active_timeout(token)}"
    await redis.set(key, value)
